String.prototype.reverse = function () {
    return this.split('').reverse().join('')
};

String.prototype.toSize = function () {
    let size = this;
    if (!!size && size > 0) {
        const KB = 1024;
        const MB = 1024 * KB;
        const GB = 1024 * MB;
        const TB = 1024 * GB;
        let sizeStr = size > TB ? size / TB + "TB" : size > GB ? size / GB + "GB" : size > MB ? size / MB + "MB" : size > KB ? size / KB + "KB" : size;
        let unit = sizeStr.substr(-2);
        let number = sizeStr.replace(unit, '');
        number = Math.floor(number * 10) / 10;
        sizeStr = number + unit;
        return sizeStr;
    } else {
        return '0KB';
    }
};

String.prototype.between = function (start = '', end = '', isInner = true) {
    let str = this;
    if (!!str && str.length > 0) {
        let startIndex = 0;
        if (start) {
            let io = str.indexOf(start);
            if (io >= 0) {
                if (isInner) {
                    io = io + start.length;
                }
                startIndex = io;
            } else {
                return '';
            }
        }
        let endIndex = str.length;
        if (end) {
            let io = str.indexOf(end, startIndex);
            if (io >= 0) {
                if (!isInner) {
                    io = io + end.length;
                }
                endIndex = io;
            } else {
                return '';
            }
        }
        return str.substring(startIndex, endIndex);
    } else {
        return '';
    }
};

let UUID = (len) => {
    let max = 520 << 520;
    len = len === null || isNaN(len) || len > max ? 6 : len;
    let uuid = '';
    let rand = () => ('' + Math.random()).substring(2);
    do {
        uuid += rand();
    } while (uuid.length < len) ;
    return uuid.substring(0, len);
};

let trimQuote = (str) => {
    let trimStr = String(str).trim();
    if (!!trimStr) {
        let quote = '"';
        if (trimStr.startsWith(quote)) {
            trimStr = trimStr.substr(1);
        }
        if (trimStr.endsWith(quote)) {
            trimStr = trimStr.substring(0, trimStr.lastIndexOf(quote));
        }
    }
    return trimStr;
};

/**
 * 获取字符串的 哈希值
 * @param str 字符串
 * @param caseSensitive 区分大小写
 * @returns {number}
 */
let getHashCode = (str, caseSensitive = true) => {
    str = '' + str;
    if (!caseSensitive) {
        str = str.toLowerCase();
    }
    let hash = 13709061665, i, ch;
    for (i = str.length - 1; i >= 0; i--) {
        ch = str.charCodeAt(i);
        hash ^= ((hash << 5) + ch + (hash >> 2));
    }
    return (hash & 2147483647);
};

/**
 * fake flat for webStorm,idea
 * @type {string}
 */
let fakeU = 'fake webStorm,idea';
if (!fakeU) {
    Array.prototype.flat = () => Array.prototype.flat();
    /**
     *
     * @param url{string}
     * @param param{object}
     * @returns {object}
     */
    let axiosParam = (url, param = {}) => Object.assign(url, param);
    /**
     *
     * @type {{request: (function(string, Object): string & Object), post: (function(string, Object): string & Object), get: (function(string, Object): string & Object), delete: (function(string, Object): string & Object), put: (function(string, Object): string & Object)}}
     */
    axios = {
        get: axiosParam,
        put: axiosParam,
        post: axiosParam,
        delete: axiosParam,
        request: axiosParam,
    };
    Vue = {
        getCurrentInstance: () => 0,
        // @see <a href="https://blog.csdn.net/weixin_34370347/article/details/89617464">Vue获取组件name属性</a>
        $options: {},
        globalProperties: {},
        $emit: () => 0,
        mount: () => 0,
        unmount: () => 0,
        createApp: () => 0,
        reactive: () => 0,
        $el: {},
    };
    /**
     * js-sha1
     * @see https://github.com/emn178/js-sha1
     * @param str
     * @returns {string}
     */
    sha1 = (str) => '';
    VueRouter = {
        createRouter: () => 0,
        createWebHashHistory: () => 0,
    };
    /**
     * js 代码格式化工具
     * @link https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.11.0/beautify.min.js
     * @param jsCode js脚本文本
     * @returns {string}
     */
    js_beautify = (jsCode) => String(jsCode);
}

/**
 * 验证脚本合法性
 * @returns {boolean}
 */
String.prototype.validScript = function () {
    let scriptStrObj = this;
    try {
        // 这里要转换为字符串才能正确验证, 否则传入的是String()对象, 无法验证
        eval(String(scriptStrObj));
        return true;
    } catch (e) {
        console.error('无效的脚本: %o, error: {}', scriptStrObj, e);
    }
    return false;
};

/**
 *  禁止用F5键
 * @returns {boolean}
 */
document.onkeydown = () => {
    console.log('event.keyCode', event.keyCode);
    if (event.keyCode === 116) {
        //event.keyCode = 0;
        //event.cancelBubble = true;
        //// window.location.href = '/';
        // return false;
    }
};

/**
 * 从数组中的对象中查找key键名对应的值包含被查找的值数组
 * @param sourceArr 源对象数组
 * @param key 数组中对象的key键名
 * @param valuesArr key的值数组
 * @returns {*}
 */
let findKey = (sourceArr, key, valuesArr) => sourceArr.filter(e => valuesArr.filter(f => f === e[key]).length === 1);

let randNumber = (min, max) => parseInt(Math.random() * (max - min + 1) + min, 10);

let deepClone = (obj) => JSON.parse(JSON.stringify(obj));

/**
 * 日期转指定格式字符串
 * @param fmt 日期格式化函数
 * @returns {string} 格式化后的日期字符串
 * @modified zhongbo
 * @date 2020/5/22
 */
Date.prototype.fmt = function dateFormat(fmt = 'MM月dd日HH:mm:ss,SSS') {
    //eg: yyyy-MM-dd HH:mm:ss
    let date = this;
    let ret;
    let opt = {
        "y+": date.getFullYear().toString(),        // 年
        "Y+": date.getFullYear().toString(),        // 年
        "M+": (date.getMonth() + 1).toString(),     // 月
        "d+": date.getDate().toString(),            // 日
        "D+": date.getDate().toString(),            // 日
        "H+": date.getHours().toString(),           // 时
        "m+": date.getMinutes().toString(),         // 分
        "s+": date.getSeconds().toString(),          // 秒
        "S+": date.getMilliseconds().toString()          // 秒
        // 有其他格式化字符需求可以继续添加，必须转化成字符串
    };
    for (let k in opt) {
        ret = new RegExp("(" + k + ")").exec(fmt);
        if (ret) {
            fmt = fmt.replace(ret[1], (ret[1].length === 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
        }
    }
    return fmt;
};

/**
 * 秒转时间字符串
 * @param second
 * @returns {string}
 */
let timeUnit = (second) => {
    if (!!Math.floor(second)) {
        const MINUTE = 60;
        const HOUR = 60 * MINUTE;
        const DAY = 24 * HOUR;
        let timeStr = second > DAY ? second / DAY + '天' : second > HOUR ? second / HOUR + '时' : second > MINUTE ? second / MINUTE + '分' : second + '秒';
        let unit = timeStr.substr(-1);
        let number = timeStr.replace(unit, '');
        number = Math.floor(number * 10) / 10;
        timeStr = number + unit;
        return timeStr;
    } else {
        return '0秒'
    }
};

/**
 * 按键事件检测
 *
 * 特殊按键说明
 * metaKey : 即Win键
 * ctrlKey : 即Ctrl键
 * altKey : 即Alt键
 * shiftKey : 即Shift键
 * 'Tab' : 即Tab键
 * 'Escape' : 即Esc键
 * @param {KeyboardEvent} event
 * @param {array} param
 * @return {boolean}
 */
let isEventKey = (event, param = []) => {
    let matchAllKey = true;
    // 判断 event 按键事件需要使用 event.nativeEvent 来判断事件类型
    if (!!event && !!param && event.nativeEvent instanceof KeyboardEvent && param instanceof Array) {
        param.forEach(key => {
            if (matchAllKey) {
                if ('ctrl' === key) {
                    matchAllKey = event.ctrlKey;
                } else if ('alt' === key) {
                    matchAllKey = event.altKey;
                } else {
                    matchAllKey = event.key === key;
                }
            }
        })
    } else {
        matchAllKey = false;
    }
    return matchAllKey;
};

/**
 * 字符串中间插入文本
 * @param sourceStr
 * @param start
 * @param insertStr
 * @return {*}
 */
let insertStr = (sourceStr, start, insertStr) => {
    if (!!sourceStr && !!insertStr && !isNaN(start) && sourceStr instanceof String && insertStr instanceof String) {
        return sourceStr.slice(0, start) + insertStr + sourceStr.slice(start);
    }
    return sourceStr;
};

/**
 * 数组中间插入内容
 * @param {array} sourceArr
 * @param {number} start
 * @param {*} insertObj
 * @return {*}
 */
let insertArr = (sourceArr, start, insertObj) => {
    if (!!sourceArr && !!insertObj) {
        sourceArr = _.clone(sourceArr);
        if (sourceArr instanceof Array) {
            // 判断超越最大下标
            if (sourceArr[start] === undefined) {
                sourceArr.push(insertObj);
            } else {
                // 中间添加一行
                sourceArr = sourceArr.map((v, i) => i === start ? [v, insertObj] : v).flat();
            }
        }
    }
    return sourceArr;
};

/**
 * 数组中间插入内容
 * @param {array} sourceArr
 * @param {number} start
 * @return {*}
 */
let removeArr = (sourceArr, start) => {
    if (!!sourceArr && typeof start === 'number') {
        sourceArr = _.clone(sourceArr);
        if (sourceArr instanceof Array) {
            // 中间删除一行
            sourceArr = sourceArr.filter((url, index) => index !== start);
        }
    } else {
        console.log('移除数组错误! 参数错误 sourceArr: %o start: %o', sourceArr, start);
    }
    return sourceArr;
};

/**
 * 字节转字符串
 * @param byteSize
 * @returns {string}
 */
let byteUnit = (byteSize) => {
    if (!!Math.floor(byteSize)) {
        const KB = 1024;
        const MB = 1024 * KB;
        const GB = 1024 * MB;
        let timeStr = byteSize > GB ? byteSize / GB + 'GB' : byteSize > MB ? byteSize / MB + 'MB' : byteSize > KB ? byteSize / KB + 'KB' : byteSize + 'B';
        let unit = timeStr.substr(-1);
        let unitHigh = timeStr.substr(-2, 1);
        unit = unitHigh > '9' ? unitHigh + unit : unit;
        let number = timeStr.replace(unit, '');
        number = Math.floor(number * 10) / 10;
        timeStr = number + unit;
        return timeStr;
    } else {
        return '0B'
    }
};

/**
 * 判断是否管理员
 * @return {boolean}
 */
let isAdmin = () => !!localStorage.getItem('token');

/**
 * 判断字符串是否为json
 * @param str
 * @returns {boolean}
 * @see <a href="https://www.cnblogs.com/lanleiming/p/7096973.html">【最简单的方法】js判断字符串是否为JSON格式（20180115更新）</a>
 */
let isJson = (str) => {
    if (typeof str === 'string') {
        try {
            let obj = JSON.parse(str);
            return !!(typeof obj === 'object' && obj);
        } catch (e) {
            console.log('isJson error： %o !!!', str, e);
        }
    }
    return false;
};

/**
 * 提取日志打印FormData信息
 * @param params 请求参数
 * @returns {object}
 */
let extractFormData = (params) => FormData && params && params instanceof FormData ? (() => {
    let next, returnObj = {}, pk = params.keys();
    while ((next = pk.next()) && !next.done) returnObj[next.value] = params.getAll(next.value);
    return returnObj;
})() : params;

//console.__proto__.blue = {
//    log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ')
//        , 'background-color:blue;color:white;line-height:20px;', new Date().fmt(), ...e)
//};
//
//console.__proto__.green = {
//    log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ')
//        , 'background-color:blue;color:blue;line-height:20px;', new Date().fmt(), ...e)
//};
//
//console.__proto__.yellow = {
//    log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ')
//        , 'background-color:blue;color:yellow;line-height:20px;', new Date().fmt(), ...e)
//};
//
//console.__proto__.green = {
//    log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ')
//        , 'background-color:blue;color:green;line-height:20px;', new Date().fmt(), ...e)
//};

/**
 * 编码转换
 *
 * 使用示例:
 * let str1 = charConvert('{"code":0,"msg":"æ\x93\x8Dä½\x9Cæ\x88\x90å\x8A\x9F","data":[]}', 'latin1', 'utf8')
 * let str1 = charConvert(str1,'utf-8','latin1')
 *
 * https://github.com/polygonplanet/encoding.js
 * https://unpkg.com/encoding-japanese@2.0.0/encoding.min.js
 *
 * @param str 字符串
 * @param from 原编码
 * @param to 目标编码
 * @returns {string|*} 转换后的字符串
 */
let charConvert = (str, from, to) => {
    if (typeof str !== 'string') return str;
    if (typeof Encoding === 'undefined') {
        console.log('encoding.js is not found!');
        return str;
    }
    let arrFrom = Encoding.stringToCode(str, from);
    let arrTo = Encoding.convert(arrFrom, from, to);
    return Encoding.codeToString(arrTo, to);
};

/**
 * Blob转换为base64互相转换
 * Blob序列化, 反序列化工具
 *
 * @see <a href="https://github.com/angadn/blob64">blob64</a>
 * @type {{serialize(*, *): void, deserialize(*): Blob}}
 */
let blob64 = {
    /**
     * 序列化
     * @param blob  Blob对象
     * @param onFinish 完成回调
     */
    serialize(blob, onFinish) {
        let reader = new FileReader();
        reader.readAsDataURL(blob);
        reader.onloadend = function () {
            onFinish(reader.result);
        };
    },
    /**
     * 序列化 Promise版
     * 推荐使用
     *
     * @param blob  Blob对象
     * @returns {Promise<String>} 序列化后的字符串
     */
    serializePromise(blob) {
        return new Promise(resolve => {
            this.serialize(blob, resolve);
        });
    },
    /**
     * 反序列化
     * @param str base64字符串
     * @returns {Blob} Blob对象
     */
    deserialize(str) {
        let indexOfComma = str.lastIndexOf(",");
        let contentType = str.substr(0, indexOfComma + 1) || "";
        let byteChars = atob(str.substr(indexOfComma + 1));
        let sliceSize = 512;
        let byteArrays = [];
        for (let offset = 0; offset < byteChars.length; offset += sliceSize) {
            let slice = byteChars.slice(offset, offset + sliceSize);
            let byteNumbers = new Array(slice.length);
            for (let i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }
            byteArrays.push(new Uint8Array(byteNumbers));
        }
        return new Blob(byteArrays, {type: contentType});
    },
    /**
     * 反序列化 Promise版
     * 推荐使用
     *
     * @see <a href="https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript">stackoverflow</a>  * @param str base64字符串
     * @returns {Blob} Blob对象
     */
    deserializePromise: (str) => fetch(str).then(res => res.blob()),
    /**
     * 文本转换为Blob对象
     *
     * 常用: text/plain, text/html, text/html;charset=charset=utf-8;,
     * application/octet-stream, text/css, text/javascript, application/javascript,
     * application/json, application/xml, application/x-javascript, text/xml,
     * application/xhtml+xml, application/x-www-form-urlencoded, multipart/form-data
     *
     * @see <a href="https://github.com/kevin-roark/get-text-blob">get-text-blob</a>
     * @param text 文本
     * @param mimeType MIME类型, 默认text/plain
     * @returns {null} Blob对象
     */
    textToBlob(text, mimeType = 'text/plain') {
        let blob = null;
        try {
            if (text.startsWith('data:')) {
                blob = this.deserialize(text);
            } else {
                blob = new Blob([text], {type: mimeType});
            }
        } catch (e) {
            // TypeError old chrome and FF
            let blobBuilder = window['BlobBuilder'] ||
                window['WebKitBlobBuilder'] ||
                window['MozBlobBuilder'] ||
                window['MSBlobBuilder'];
            if (blobBuilder) {
                let bb = new blobBuilder();
                bb.append([text]);
                blob = bb['getBlob']('text/html');
            }
        }
        return blob;
    },
    /**
     * Blob对象转换为文本
     *
     * @see <a href="https://blog.csdn.net/qq_46090071/article/details/124199827">js在blob对象中读取文字内容</a>
     * @param blob Blob对象
     * @param charSet 字符集
     * @returns {Promise<String>} 文本
     */
    blobToTextPromise(blob, charSet = null) {
        return new Promise(resolve => {
            let reader = new FileReader();
            reader.onloadend = function () {
                resolve(reader.result);
            };
            if (charSet) {
                reader.readAsText(blob, charSet);
            } else {
                reader.readAsText(blob);
            }
        });
    },
    /**
     * 下载Blob对象到文件
     *
     * @see <a href="https://pixijs.io/examples/#/demos-advanced/screenshot.js">screenshot</a>
     * @param blob Blob对象
     * @param fileName 文件名
     */
    downLoadBlob(blob, fileName) {
        let a = document.createElement('a');
        a.href = window.URL.createObjectURL(blob);
        a.download = fileName;
        a.click();
        window.URL.revokeObjectURL(a.href);
    },
    /**
     * 下载Blob对象到文件
     *
     * @see <a href="https://pixijs.io/examples/#/demos-advanced/screenshot.js">screenshot</a>
     * @param blob Blob对象
     * @param fileName 文件名
     */
    downBlob(blob, fileName = "download") {
        const a = document.createElement('a');
        document.body.append(a);
        a.download = fileName;
        a.href = URL.createObjectURL(blob);
        a.click();
        a.remove();
        window.URL.revokeObjectURL(a.href);
    },
}

/**
 * 禁用调试模式
 * @see <a href="https://zmingcx.com/wp-content/cache/autoptimize/js/autoptimize_e7a8a795e88cf81ddfa417af57368243.js">提取自源码forbidDebug()</a>
 */
let forbidDebug = function () {
    // 是否允许调试
    let enableDebug = true;
    if (enableDebug) {
        return;
    }
    try {
        ((function () {
            let callbacks = [], timeLimit = 50, open = false;
            setInterval(loop, 1);
            return {
                addListener: function (fn) {
                    callbacks.push(fn)
                }, cancleListenr: function (fn) {
                    callbacks = callbacks.filter(function (v) {
                        return v !== fn
                    })
                }
            };

            function loop() {
                let startTime = new Date();
                //de//bugger;
                if (new Date() - startTime > timeLimit) {
                    if (!open) {
                        callbacks.forEach(function (fn) {
                            fn.call(null)
                        })
                    }
                    open = true;
                    window.stop();
                    alert("\u5173\u95ed\u63a7\u5236\u53f0\u540e\u5237\u65b0\uff01");
                    document.body.innerHTML = ""
                } else {
                    open = false
                }
            }
        })()).addListener(function () {
            window.location.reload()
        })
    } catch (e) {
    }
};
forbidDebug();

/**
 * 自制禁用调试
 */
(() => {
    // 是否启用
    let enable = false;
    let c = String.fromCharCode;
    let d = [40, 40, 41, 61, 62, 123, 100, 101, 98, 117, 103, 103, 101, 114, 125, 41, 40, 41];
    try {
        enable && eval(d.map(e => c(e)).join(''));
    } catch (e) {
        console.error(e);
        window.stop();
    }
})();

